#include "AI_Perceiver.h"
#include "Engine.h"
#include "GameState.h"
#include "Frustum.h"
#include "DirectXMesh.h"
#include "BPhysicsActor.h"

using namespace Perception;

/***********************************************************************   */
/* MANAGEMENT                                                              */
/***********************************************************************   */
void Ai_Perception_Manager::Update()
{
    m_framesSinceLastRender++;
    if(m_framesSinceLastRender > m_maxFrameDelay || 
        m_requestCache.size() > m_cacheThreshold)
    {
        RenderRequests();
        m_framesSinceLastRender = 0;
    }
}
void Ai_Perception_Manager::RenderRequests()
{
    for(unsigned int i = 0; i < m_requestCache.size(); ++i)
    {
        Ai_Perception_Cache* c = m_requestCache[i];
        ID3DXEffect* effect = Engine::Instance()->GetCurrentState()->GetEffect();
        effect->SetTechnique("Ai_Perception_Tech");
        Engine::Instance()->SetRenderTarget(c->pRenderTarget, true);
        // Set the projection matrix
        TEST( effect->SetMatrix( "g_mProj", c->pProjMat ) );
        TEST( effect->SetMatrix( "g_mView", c->pViewMat ) );
        RenderObjects(effect);
        Engine::Instance()->ResetRenderTarget();     
        c->m_finished = true;
        GenerateBuffer(c);
    }
    m_requestCache.clear();
}
void Ai_Perception_Manager::RenderObjects(ID3DXEffect* effect)
{
    for(unsigned int i = 0; i < m_objects.size(); ++i)
    {
        if(m_objects[i]->GetOwner()->IsAwake())
            m_objects[i]->Render(effect);
    }
}
void Ai_Perception_Manager::Initialize(int maxFramewait, int minFrameWait, int requestThreshold)
{
    m_cacheThreshold = requestThreshold;
    m_minFrameDelay = minFrameWait;
    m_maxFrameDelay = maxFramewait;
    Ai_Perception_Vertex::RegisterDeclarations();
}
D3DXVECTOR3 Ai_Perception_Manager::GetColor( Ai_Perception_Key key )
{
    std::map<Ai_Perception_Key, D3DXVECTOR3>::iterator it;
    it = m_colorKey.find(key);
    if(it != m_colorKey.end())
    {
        return it->second;
    }
    assert(0);
    return D3DXVECTOR3(0,0,0);
}
void Ai_Perception_Manager::LAddColorCode( int key, float r, float g, float b )
{
    Ai_Perception_Key k = (Ai_Perception_Key)key;
    D3DXVECTOR3 color(r,g,b);
    m_colorKey[k] = color;
}
void Ai_Perception_Manager::RegisterObject( Ai_Perception_Obj* object )
{
    m_objects.push_back(object);
}
void Ai_Perception_Manager::UnregisterAllObjects()
{
    m_objects.clear();
    m_requestCache.clear();
    m_framesSinceLastRender = 0;
}
void Ai_Perception_Manager::DebugRenderObjects()
{
    ID3DXEffect* effect = Engine::Instance()->GetCurrentState()->GetEffect();
    effect->SetTechnique("Ai_Perception_Tech");

    TEST( effect->SetMatrix( "g_mProj",&Engine::Instance()->GetCurrentState()->GetCamera()->GetProjectionMatrix() ) );
    TEST( effect->SetMatrix( "g_mView", &Engine::Instance()->GetCurrentState()->GetCamera()->GetViewMatrix()) );
    RenderObjects(effect);
}
void Ai_Perception_Manager::GenerateBuffer(Ai_Perception_Cache* cache)
{
    //data will be copied into this
    LPDIRECT3DTEXTURE9 dataTarget;
    int resX = cache->owner->GetResX();
    int resY = cache->owner->GetResY();
    TEST(Engine::Instance()->GetDevice()->CreateTexture(resX,resY,
                                                        0, D3DUSAGE_DYNAMIC,
                                                        D3DFMT_A8R8G8B8,
                                                        D3DPOOL_SYSTEMMEM, 
                                                        &dataTarget,
                                                        NULL ));
    //store the target surface level here
    LPDIRECT3DSURFACE9 targetSurface;
    dataTarget->GetSurfaceLevel(0,&targetSurface);
    //store the source surface here
    LPDIRECT3DSURFACE9 sourceSurface;
    cache->owner->GetPerceptionTexture()->GetSurfaceLevel(0,&sourceSurface);
    //Copy data from source into target
    Engine::Instance()->GetDevice()->GetRenderTargetData(sourceSurface, targetSurface);

    //LOCK the rectangle
    D3DLOCKED_RECT rect;
    TEST(targetSurface->LockRect(&rect,NULL, D3DLOCK_READONLY));

    //Check that this is all valid
    if(rect.Pitch != ( 4/* bytes per pixel*/ * resX))
        assert(0);
    unsigned int bufferSize = resX*resY;

    //copy the data across line by line (accoutning for extra buffering if it exists)
    PixelColor8* buffer = cache->owner->GetBuffer();
    memset(buffer,1,sizeof(PixelColor8)*bufferSize);
    size_t pixelSize = sizeof(PixelColor8);
    for(int i =0; i < resY; ++i)
    {
        memcpy( &buffer[0] + (i*resX),
            (PixelColor8*)rect.pBits + (i * resX), 
            rect.Pitch);
    }
    dataTarget->UnlockRect(0);
    sourceSurface->Release();
    dataTarget->Release();
    targetSurface->Release();
}
/***********************************************************************  */
/* PERCEPTION                                                             */
/***********************************************************************  */
void Ai_Perceiver::Initialize( int x, int y )
{
    //create the two textures
    TEST(Engine::Instance()->GetDevice()->CreateTexture( x, y,
        0, D3DUSAGE_RENDERTARGET,
        D3DFMT_A8R8G8B8,
        D3DPOOL_DEFAULT, 
        &m_perceptionTex,
        NULL ));
    m_request->pRenderTarget = m_perceptionTex;
    m_resX = x;
    m_resY = y;
    m_colorBuf = new PixelColor8[x*y];
    memset(m_colorBuf,0,sizeof(PixelColor8)*m_resX*m_resY);
}
void Ai_Perceiver::CleanUp()
{
    if(m_request)
    {
        delete m_request;
        m_request = NULL;
    }
    if(m_perceptionTex)
    {
        m_perceptionTex->Release();
        m_perceptionTex = NULL;
    }
    if(m_colorBuf)
    {
        delete[] m_colorBuf;
        m_colorBuf = NULL;
    }
}
void Ai_Perceiver::NewRequest()
{
    m_hasActiveRequest = true;
    Ai_Perception_Manager::Instance()->NewRequest(m_request);
}
bool Ai_Perceiver::TextureReady()
{
    if(m_request->m_finished && m_hasActiveRequest)
    {
        m_hasActiveRequest = false;
        return true;
    }
    return false;
}
void Ai_Perceiver::SetProjectionMatrixPerspective( float field, float aspect, float n, float f )
{
    m_fOv = field;
    m_aspect = aspect;
    m_n = n; 
    m_f = f;
    D3DXMatrixPerspectiveFovLH(&m_ProjMatrix,m_fOv, m_aspect, m_n, m_f);
}
void Ai_Perceiver::SetProjectionMatrixOrthographic( float width, float height, float n, float f )
{
    m_w = width/2;
    m_h = height/2;
    m_n = n; 
    m_f = f;
    D3DXMatrixOrthoLH(&m_ProjMatrix,m_w, m_h, m_n, m_f);
}
void Ai_Perceiver::SetViewMatrix( const D3DXVECTOR3& eye, const D3DXVECTOR3& look,const D3DXVECTOR3& up )
{
    m_eye = eye;    
    m_look = eye + look;
    m_eye.y += 3;
    m_look.y += 3;
    m_up = up;
    D3DXMatrixLookAtLH(&m_ViewMatrix, &m_eye, &m_look, &m_up);
}
void Ai_Perceiver::RenderDebugInfo()
{
    Frustum f;
    if(m_owner->GetProjectionType() == 0)//perspective
        f.SetProjectionInternalsPerspective((m_fOv/*/(float)M_PI)*180*/), m_aspect, m_n, m_f);
    else //orthographic
        f.SetProjectionInternalsOrthographic(m_w, m_h, m_n, m_f);

    f.SetViewInternals(Vec3(m_eye.x,m_eye.y,m_eye.z), 
                       Vec3(m_look.x,m_look.y,m_look.z),
                       Vec3(m_up.x,m_up.y,m_up.z));
    f.Draw();
}
D3DXVECTOR3 Perception::Ai_Perceiver::GetDirection( int x, int y )
{
    int xP = x;
    int yP = y;

    int screenWidth = m_resX;
    int screenHeight = m_resY;

    const D3DXMATRIX pmatProj = m_ProjMatrix;

    // Compute the vector of the pick ray in screen space
    D3DXVECTOR3 v;
    v.x = ( ( ( 2.0f * xP ) / screenWidth ) - 1 ) / pmatProj._11;
    v.y = -( ( ( 2.0f * yP ) / screenHeight ) - 1 ) / pmatProj._22;
    v.z = 1.0f;

    // Get the inverse view matrix
    D3DXMATRIX mView = m_ViewMatrix;
    D3DXMATRIX inv;
    D3DXMatrixIdentity(&inv);
    D3DXMatrixInverse( &inv, NULL, &mView );

    // Transform the screen space pick ray into 3D space
    D3DXVECTOR3 rayDir;
    rayDir.x = v.x * inv._11 + v.y * inv._21 + v.z * inv._31;
    rayDir.y = v.x * inv._12 + v.y * inv._22 + v.z * inv._32;
    rayDir.z = v.x * inv._13 + v.y * inv._23 + v.z * inv._33;
    D3DXVec3Normalize(&rayDir,&rayDir);
    return rayDir;
}

/***********************************************************************   */
/*  OBJECT                                                                 */
/***********************************************************************   */
void Ai_Perception_Obj::Render( ID3DXEffect* effect )
{
    D3DXMATRIXA16 world;
    world = *m_transformMatrix;
    D3DXMATRIXA16 scale;
    D3DXMatrixIdentity(&scale);
    D3DXMatrixScaling(&scale, m_mesh.m_scaleX,m_mesh.m_scaleY,m_mesh.m_scaleZ);
    world = scale*world;
    TEST(effect->SetMatrix("g_mWorld",&world));
    m_mesh.Render(effect);
}
void Ai_Perception_Obj::CreateAsCube( float scaleX, float scaleY, float scaleZ )
{
    m_mesh.CreateAsCube(scaleX,scaleY,scaleZ, m_colorLookUp);
}
void Ai_Perception_Obj::CreateFromMesh( DirectXMesh* mesh,float scaleX, float scaleY, float scaleZ )
{
    m_mesh.CreateFromMesh(mesh,m_colorLookUp,scaleX,scaleY,scaleZ);
}

/***********************************************************************   */
/* MESHES                                                                  */
/***********************************************************************   */
IDirect3DVertexDeclaration9* Ai_Perception_Vertex::s_dcl = 0;
bool Ai_Perception_Vertex::RegisterDeclarations()
{
    D3DVERTEXELEMENT9 VertexElements[] =
    {
        {
            0,0,D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,0
        },
        {
        0, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,0
        },
        D3DDECL_END()   
    };
    Engine::Instance()->GetDevice()->CreateVertexDeclaration(VertexElements, &Ai_Perception_Vertex::s_dcl );
    return true;
}
void Ai_Perception_Mesh::Render(ID3DXEffect* effect)
{
    UINT passes = 0;
    TEST(Engine::Instance()->GetDevice()->SetVertexDeclaration(Ai_Perception_Vertex::s_dcl));
    TEST(Engine::Instance()->GetDevice()->SetStreamSource( 0, m_vBuffer , 0, sizeof( Ai_Perception_Vertex ) ));

    TEST(effect->Begin( &passes, 0 ));

    for(unsigned int  iPass = 0; iPass < passes; iPass++ )
    {
        TEST(effect->BeginPass( iPass ));

        TEST(Engine::Instance()->GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST, 0, (m_numVerts/3) ));

        TEST(effect->EndPass());
    }
    TEST(effect->End());
}
void Ai_Perception_Mesh::CreateAsCube( float scaleX, float scaleY, float scaleZ, Ai_Perception_Key key )
{
    D3DXVECTOR3 color = Perception::Ai_Perception_Manager::Instance()->GetColor(key);
    m_scaleX = scaleX; m_scaleY = scaleY; m_scaleZ = scaleZ;
    //points on a cube, 2 triangles per face,
    int numTriangles = 12;
    m_numVerts = numTriangles * 3;
    Ai_Perception_Vertex* vertArray = new Ai_Perception_Vertex[m_numVerts];
    //0 the data
    memset(vertArray,0, sizeof(Ai_Perception_Vertex) * m_numVerts);

    //8 corners of a cube...
    Ai_Perception_Vertex array[8] = 
    {
        /* 0 */Ai_Perception_Vertex(-1,-1,-1, color.x, color.y, color.z),
        /* 1 */Ai_Perception_Vertex(-1,1,-1, color.x, color.y, color.z),
        /* 2 */Ai_Perception_Vertex(1,1,-1, color.x, color.y, color.z),
        /* 3 */Ai_Perception_Vertex(1,-1,-1, color.x, color.y, color.z),
        /* 4 */Ai_Perception_Vertex(-1,-1,1, color.x, color.y, color.z),
        /* 5 */Ai_Perception_Vertex(-1,1,1, color.x, color.y, color.z),
        /* 6 */Ai_Perception_Vertex(1,1,1, color.x, color.y, color.z),
        /* 7 */Ai_Perception_Vertex(1,-1,1, color.x, color.y, color.z)
    };
    //the -z face
    //tri 1
    vertArray[0] = array[0];
    vertArray[1] = array[1];
    vertArray[2] = array[2];
    //tri2
    vertArray[3] = array[0];
    vertArray[4] = array[2];
    vertArray[5] = array[3];

    // the +z face
    //tri 1
    vertArray[6] = array[5];
    vertArray[7] = array[4];
    vertArray[8] = array[6];
    //tri2
    vertArray[9] = array[6];
    vertArray[10] = array[4];
    vertArray[11] = array[7];


    // the +x face 2,3,6,7
    //tri 1
    vertArray[12] = array[3];
    vertArray[13] = array[2];
    vertArray[14] = array[6];
    //tri2
    vertArray[15] = array[3];
    vertArray[16] = array[6];
    vertArray[17] = array[7];


    // the -x face 0,1,4,5
    //tri 1
    vertArray[18] = array[1];
    vertArray[19] = array[0];
    vertArray[20] = array[4];
    //tri2
    vertArray[21] = array[4];
    vertArray[22] = array[5];
    vertArray[23] = array[1];

    // the +y face  1,2,5,6
    //tri 1
    vertArray[24] = array[2];
    vertArray[25] = array[1];
    vertArray[26] = array[5];
    //tri2
    vertArray[27] = array[5];
    vertArray[28] = array[6];
    vertArray[29] = array[2];

    // the -y face   0,3,4,7
    //tri 1
    vertArray[30] = array[0];
    vertArray[31] = array[3];
    vertArray[32] = array[4];
    //tri2
    vertArray[33] = array[7];
    vertArray[34] = array[4];
    vertArray[35] = array[3];

    //create a vertex buffer on GPU
    TEST(Engine::Instance()->GetDevice()->CreateVertexBuffer( m_numVerts*  sizeof(Ai_Perception_Vertex),
        D3DUSAGE_WRITEONLY, 0,
        D3DPOOL_DEFAULT , &m_vBuffer, NULL )); 

    VOID* pVertices;
    int BufferSize = sizeof( Ai_Perception_Vertex )* m_numVerts;

    m_vBuffer->Lock( 0, BufferSize, ( void** )&pVertices, 0); 

    memcpy( pVertices, vertArray, BufferSize );

    TEST(m_vBuffer->Unlock());

    delete[] vertArray;

    // create the sphere
    //LPD3DXMESH mesh;
    //TEST(D3DXCreateBox(Engine::Instance()->GetDevice(),m_scaleX,m_scaleY,m_scaleZ,&mesh,NULL));

    //// create a copy of the mesh with texture coordinates,
    //// since the D3DX function doesn't include them
    //LPD3DXMESH texMesh;
    //D3DVERTEXELEMENT9 VertexElements[] =
    //{
    //    {
    //        0,0,D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,0
    //    },
    //    {
    //        0, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,0
    //    },
    //        D3DDECL_END()   
    //};
    //TEST(mesh->CloneMesh(0, VertexElements, Engine::Instance()->GetDevice(), &texMesh ));
    //// finished with the original mesh, release it
    //mesh->Release();

    //// lock the vertex buffer
    //VOID* p;
    //TEST(texMesh->LockVertexBuffer(0, &p));
    ////cast the pointer
    //Ai_Perception_Vertex* pVerts = (Ai_Perception_Vertex*)p;    
    //// get vertex count
    //m_numVerts = texMesh->GetNumVertices();
    ////make a copy
    //Ai_Perception_Vertex* nMesh = new Ai_Perception_Vertex[m_numVerts];
    //memcpy(nMesh, pVerts, sizeof(Ai_Perception_Vertex)*m_numVerts);
    ////change the color
    //for(int i = 0; i < m_numVerts; ++i)
    //    nMesh[i].color = D3DXVECTOR4(color,1);
    //// unlock the vertex buffer
    //texMesh->UnlockVertexBuffer();

    ////store the new buffer...

    ////create a vertex buffer on GPU
    //TEST(Engine::Instance()->GetDevice()->CreateVertexBuffer( m_numVerts*  sizeof(Ai_Perception_Vertex),
    //    D3DUSAGE_WRITEONLY, 0,
    //    D3DPOOL_DEFAULT , &m_vBuffer, NULL )); 

    //VOID* pVertices;
    //int BufferSize = sizeof( Ai_Perception_Vertex )* m_numVerts;

    //m_vBuffer->Lock( 0, BufferSize, ( void** )&pVertices, 0); 

    //memcpy( pVertices, nMesh, BufferSize );

    //TEST(m_vBuffer->Unlock());

    //delete[] nMesh;
    
}
void Ai_Perception_Mesh::CreateAsSphere( float radius, Ai_Perception_Key key )
{
//not implemented atm
}
void Ai_Perception_Mesh::CleanUp()
{
    if(m_vBuffer)
        m_vBuffer->Release();
    m_vBuffer = 0;
}
void Ai_Perception_Mesh::CreateFromMesh( DirectXMesh* mesh, Ai_Perception_Key key,
                                        float scaleX, float scaleY, float scaleZ)
{
    D3DXVECTOR3 color = Perception::Ai_Perception_Manager::Instance()->GetColor(key);
    m_scaleX =scaleX;
    m_scaleY =scaleY;
    m_scaleZ =scaleZ;
    //points on a cube, 2 triangles per face,
    int numTriangles = mesh->Triangles().size();
    m_numVerts = numTriangles * 3;
    Ai_Perception_Vertex* vertArray = new Ai_Perception_Vertex[m_numVerts];
    //0 the data
    memset(vertArray,0, sizeof(Ai_Perception_Vertex) * m_numVerts);

    LPDIRECT3DVERTEXBUFFER9 buf = mesh->GenerateSingleBuffer();
    CustomVertex* pVertices;
    int tempBufSize = sizeof(CustomVertex) * m_numVerts;
    buf->Lock( 0, tempBufSize, ( void** )&pVertices, 0); 
    for(int i = 0; i < m_numVerts; ++i)
    {
        vertArray[i].color.x = color.x;
        vertArray[i].color.y = color.y;
        vertArray[i].color.z = color.z;
        vertArray[i].pos = pVertices[i].pos;
    }
    buf->Unlock();
    buf->Release();

    //create a vertex buffer on GPU
    TEST(Engine::Instance()->GetDevice()->CreateVertexBuffer( m_numVerts*  sizeof(Ai_Perception_Vertex),
        D3DUSAGE_WRITEONLY, 0,
        D3DPOOL_DEFAULT , &m_vBuffer, NULL )); 

    VOID* memAdd;
    int BufferSize = sizeof( Ai_Perception_Vertex )* m_numVerts;
    m_vBuffer->Lock( 0, BufferSize, ( void** )&memAdd, 0); 
    memcpy( memAdd, vertArray, BufferSize );
    TEST(m_vBuffer->Unlock());
    delete[] vertArray;
}
